home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Internet Info 1994 March
/
Internet Info CD-ROM (Walnut Creek) (March 1994).iso
/
networking
/
ip
/
ka9q
/
src890906.arc
/
SMTPSERV.C
< prev
next >
Wrap
C/C++ Source or Header
|
1989-09-07
|
18KB
|
814 lines
/* SMTP Server state machine - see RFC 821
* enhanced 4/88 Dave Trulli nn2z
*/
#include <stdio.h>
#include <time.h>
#ifdef UNIX
#include <sys/types.h>
#endif
#if defined(__STDC__) || defined(__TURBOC__)
#include <stdarg.h>
#endif
#include <ctype.h>
#include <setjmp.h>
#include "global.h"
#include "mbuf.h"
#include "cmdparse.h"
#include "socket.h"
#include "proc.h"
#include "smtp.h"
extern char Nospace[];
static struct list *expandalias __ARGS((struct list **head,char *user));
static int getmsgtxt __ARGS((struct smtpsv *mp));
static struct smtpsv *mail_create __ARGS((void));
static char *getname __ARGS((char *cp));
static void mail_clean __ARGS((struct smtpsv *mp));
static int mailit __ARGS((FILE *data,char *from,struct list *tolist));
static int router_queue __ARGS((FILE *data,char *from,struct list *to));
static void smtplog __ARGS((char *fmt,...));
static void smtpserv __ARGS((int s,void *unused,void *p));
/* Command table */
static char *commands[] = {
"helo",
#define HELO_CMD 0
"noop",
#define NOOP_CMD 1
"mail from:",
#define MAIL_CMD 2
"quit",
#define QUIT_CMD 3
"rcpt to:",
#define RCPT_CMD 4
"help",
#define HELP_CMD 5
"data",
#define DATA_CMD 6
"rset",
#define RSET_CMD 7
NULLCHAR
};
/* Reply messages */
static char help[] = "214-Commands:\r\n214-HELO NOOP MAIL QUIT RCPT HELP DATA RSET\r\n214 End\r\n";
static char banner[] = "220 %s SMTP ready\r\n";
static char closing[] = "221 Closing\r\n";
static char ok[] = "250 Ok\r\n";
static char reset[] = "250 Reset state\r\n";
static char sent[] = "250 Sent\r\n";
static char ourname[] = "250 %s, Share and Enjoy!\r\n";
static char enter[] = "354 Enter mail, end with .\r\n";
static char ioerr[] = "452 Temp file write error\r\n";
static char badcmd[] = "500 Command unrecognized\r\n";
static char syntax[] = "501 Syntax error\r\n";
static char needrcpt[] = "503 Need RCPT (recipient)\r\n";
static char unknown[] = "550 <%s> address unknown\r\n";
static int Ssmtp = -1; /* prototype socket for service */
/* Start up SMTP receiver service */
int
smtp1(argc,argv,p)
int argc;
char *argv[];
void *p;
{
struct sockaddr_in lsocket;
int s;
if(Ssmtp != -1){
freeargs(argc,argv);
return 0;
}
psignal(Curproc,0); /* Don't keep the parser waiting */
chname(Curproc,"SMTP listener");
lsocket.sin_family = AF_INET;
lsocket.sin_addr.s_addr = Ip_addr;
if(argc < 2)
lsocket.sin_port = IPPORT_SMTP;
else
lsocket.sin_port = atoi(argv[1]);
freeargs(argc,argv);
Ssmtp = socket(AF_INET,SOCK_STREAM,0);
bind(Ssmtp,(char *)&lsocket,sizeof(lsocket));
listen(Ssmtp,1);
for(;;){
if((s = accept(Ssmtp,NULLCHAR,(int *)NULL)) == -1)
break; /* Service is shutting down */
/* Spawn a server */
newproc("SMTP server",2048,smtpserv,s,NULL,NULL);
}
return 0;
}
/* Shutdown SMTP service (existing connections are allowed to finish) */
int
smtp0(argc,argv,p)
int argc;
char *argv[];
void *p;
{
close_s(Ssmtp);
Ssmtp = -1;
return 0;
}
static void
smtpserv(s,unused,p)
int s;
void *unused;
void *p;
{
struct smtpsv *mp;
char **cmdp,buf[LINELEN],*arg,*cp,*cmd;
int cnt;
char address_type;
sockowner(s,Curproc); /* We own it now */
log(s,"open SMTP");
if((mp = mail_create()) == NULLSMTPSV){
printf(Nospace);
log(s,"close SMTP - no space");
close_s(s);
return;
}
mp->s = s;
(void) usprintf(s,banner,Hostname);
loop: if ((cnt = recvline(s,buf,sizeof(buf))) == -1) {
/* He closed on us */
goto quit;
}
if(cnt < 4){
/* Can't be a legal command */
usprintf(mp->s,badcmd);
goto loop;
}
rip(buf);
cmd = buf;
/* Translate entire buffer to lower case */
for(cp = cmd;*cp != '\0';cp++)
*cp = tolower(*cp);
/* Find command in table; if not present, return syntax error */
for(cmdp = commands;*cmdp != NULLCHAR;cmdp++)
if(strncmp(*cmdp,cmd,strlen(*cmdp)) == 0)
break;
if(*cmdp == NULLCHAR){
(void) usprintf(mp->s,badcmd);
goto loop;
}
arg = &cmd[strlen(*cmdp)];
/* Skip spaces after command */
while(*arg == ' ')
arg++;
/* Execute specific command */
switch(cmdp-commands) {
case HELO_CMD:
free(mp->system);
if((mp->system = strdup(arg)) == NULLCHAR){
/* If the system is out of memory, just close */
goto quit;
} else {
(void) usprintf(mp->s,ourname,Hostname);
}
break;
case NOOP_CMD:
(void) usprintf(mp->s,ok);
break;
case MAIL_CMD:
if((cp = getname(arg)) == NULLCHAR){
(void) usprintf(mp->s,syntax);
break;
}
free(mp->from);
if((mp->from = strdup(cp)) == NULLCHAR){
/* If the system is out of memory, just close */
goto quit;
} else {
(void) usprintf(mp->s,ok);
}
break;
case QUIT_CMD:
(void) usprintf(mp->s,closing);
goto quit;
case RCPT_CMD: /* Specify recipient */
if((cp = getname(arg)) == NULLCHAR){
(void) usprintf(mp->s,syntax);
break;
}
/* check if address is ok */
if ((address_type = validate_address(cp)) == BADADDR) {
(void) usprintf(mp->s,unknown,cp);
break;
}
/* if a local address check for an alias */
if (address_type == LOCAL)
expandalias(&mp->to, cp);
else
/* a remote address is added to the list */
addlist(&mp->to, cp, address_type);
(void) usprintf(mp->s,ok);
break;
case HELP_CMD:
(void) usprintf(mp->s,help);
break;
case DATA_CMD:
if(mp->to == NULLLIST)
(void) usprintf(mp->s,needrcpt);
else if ((mp->data = tmpfile()) == NULLFILE)
(void) usprintf(mp->s,ioerr);
else
getmsgtxt(mp);
break;
case RSET_CMD:
del_list(mp->to);
mp->to = NULLLIST;
(void) usprintf(mp->s,reset);
break;
}
goto loop;
quit:
log(mp->s,"close SMTP");
close_s(mp->s);
mail_clean(mp);
}
/* read the message text */
static int
getmsgtxt(mp)
struct smtpsv *mp;
{
char buf[LINELEN];
register char *p = buf;
long t;
/* Add timestamp; ptime adds newline */
time(&t);
fprintf(mp->data,"Received: ");
if(mp->system != NULLCHAR)
fprintf(mp->data,"from %s ",mp->system);
fprintf(mp->data,"by %s with SMTP\n\tid AA%ld ; %s",
Hostname, get_msgid(), ptime(&t));
if(ferror(mp->data)){
(void) usprintf(mp->s,ioerr);
return 1;
} else {
(void) usprintf(mp->s,enter);
}
while(1) {
if(recvline(mp->s,p,sizeof(buf)) == -1){
return 1;
}
rip(p);
/* check for end of message ie a . or escaped .. */
if (*p == '.') {
if (*++p == '\0') {
/* Also sends appropriate response */
if (mailit(mp->data,mp->from,mp->to) != 0)
(void) usprintf(mp->s,ioerr);
else
(void) usprintf(mp->s,sent);
fclose(mp->data);
mp->data = NULLFILE;
del_list(mp->to);
mp->to = NULLLIST;
return 0;
} else if (!(*p == '.' && *(p+1) == '\0'))
p--;
}
/* for UNIX mail compatiblity */
if (strncmp(p,"From ",5) == 0)
(void) putc('>',mp->data);
/* Append to data file */
if(fprintf(mp->data,"%s\n",p) < 0) {
(void) usprintf(mp->s,ioerr);
return 1;
}
}
return 0;
}
/* Create control block, initialize */
static struct smtpsv *
mail_create()
{
register struct smtpsv *mp;
if((mp = (struct smtpsv *)calloc(1,sizeof(struct smtpsv))) == NULLSMTPSV)
return NULLSMTPSV;
return mp;
}
/* Free resources, delete control block */
static void
mail_clean(mp)
register struct smtpsv *mp;
{
if (mp == NULLSMTPSV)
return;
free(mp->system);
free(mp->from);
if(mp->data != NULLFILE)
fclose(mp->data);
del_list(mp->to);
free((char *)mp);
}
/* Given a string of the form <user@host>, extract the part inside the
* brackets and return a pointer to it.
*/
static char *
getname(cp)
register char *cp;
{
register char *cp1;
if ((cp = strchr(cp,'<')) == NULLCHAR)
return NULLCHAR;
cp++; /* cp -> first char of name */
if ((cp1 = strchr(cp,'>')) == NULLCHAR)
return NULLCHAR;
*cp1 = '\0';
return cp;
}
/* General mailit function. It takes a list of addresses which have already
** been verified and expanded for aliases. Base on the current mode the message
** is place in an mbox, the outbound smtp queue or the rqueue interface
*/
static int
mailit(data,from,tolist)
FILE *data;
char *from;
struct list *tolist;
{
register struct list *ap;
register FILE *fp;
int c;
char mailbox[50];
char *cp;
char *desthost;
int fail = 0;
time_t t;
if ((Smtpmode & QUEUE) != 0)
return(router_queue(data,from,tolist));
for(ap = tolist;ap != NULLLIST;ap = ap->next) {
rewind(data);
/* non local mail queue it */
if (ap->type == DOMAIN) {
if ((desthost = strchr(ap->val,'@')) != NULLCHAR)
desthost++;
fail = queuejob(data,desthost,ap->val,from);
} else {
/* strip off host name */
if ((cp = strchr(ap->val,'@')) != NULLCHAR)
*cp = '\0';
/* truncate long user names */
if (strlen(ap->val) > MBOXLEN)
ap->val[MBOXLEN] = '\0';
/* if mail file is busy save it in our smtp queue
* and let the smtp daemon try later.
*/
if (mlock(Mailspool,ap->val))
fail = queuejob(data,Hostname,ap->val,from);
else {
sprintf(mailbox,"%s/%s.txt",Mailspool,ap->val);
#ifndef AMIGA
if((fp = fopen(mailbox,APPEND_TEXT)) != NULLFILE) {
#else
(void) fseek(fp, 0L, 2);
#endif
time(&t);
fprintf(fp,
"From %s %s",from,ctime(&t));
while((c = getc(data)) != EOF)
if(putc(c,fp) == EOF)
break;
if(ferror(fp))
fail = 1;
else
fprintf(fp,"\n");
/* Leave a blank line between msgs */
fclose(fp);
printf("New mail arrived for %s\n",ap->val);
} else
fail = 1;
(void) rmlock(Mailspool,ap->val);
if (fail)
break;
smtplog("deliver: To: %s From: %s",ap->val,from);
}
}
}
return fail;
}
/* Return Date/Time in Arpanet format in passed string */
char *
ptime(t)
long *t;
{
/* Print out the time and date field as
* "DAY day MONTH year hh:mm:ss ZONE"
*/
register struct tm *ltm;
static char tz[4];
static char str[40];
char *p;
static char *days[7] = {
"Sun","Mon","Tue","Wed","Thu","Fri","Sat" };
static char *months[12] = {
"Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec" };
/* Read the system time */
ltm = localtime(t);
if (*tz == '\0')
if ((p = getenv("TZ")) == NULL)
strcpy(tz,"GMT");
else
strncpy(tz,p,3);
/* rfc 822 format */
sprintf(str,"%s, %.2d %s %02d %02d:%02d:%02d %.3s\n",
days[ltm->tm_wday],
ltm->tm_mday,
months[ltm->tm_mon],
ltm->tm_year,
ltm->tm_hour,
ltm->tm_min,
ltm->tm_sec,
tz);
return(str);
}
long
get_msgid()
{
char sfilename[LINELEN];
char s[20];
register long sequence = 0;
FILE *sfile;
sprintf(sfilename,"%s/sequence.seq",Mailqdir);
sfile = fopen(sfilename,READ_TEXT);
/* if sequence file exists, get the value, otherwise set it */
if (sfile != NULL) {
(void) fgets(s,sizeof(s),sfile);
sequence = atol(s);
/* Keep it in range of and 8 digit number to use for dos name prefix. */
if (sequence < 0L || sequence > 99999999L )
sequence = 0;
fclose(sfile);
}
/* increment sequence number, and write to sequence file */
sfile = fopen(sfilename,WRITE_TEXT);
fprintf(sfile,"%ld",++sequence);
fclose(sfile);
return sequence;
}
#ifdef MSDOS
/* Illegal characters in a DOS filename */
static char baddoschars[] = "\"[]:|<>+=;,";
#endif
/* test if mail address is valid */
int
validate_address(s)
char *s;
{
char *cp;
int32 addr;
/* if address has @ in it the check dest address */
if ((cp = strrchr(s,'@')) != NULLCHAR) {
cp++;
/* 1st check if its our hostname
* if not then check the hosts file and see
* if we can resolve ther address to a know site
* or one of our aliases
*/
if (strcmp(cp,Hostname) != 0) {
if ((addr = mailroute(cp)) == 0
&& (Smtpmode & QUEUE) == 0)
return BADADDR;
if (addr != Ip_addr)
return DOMAIN;
}
/* on a local address remove the host name part */
*--cp = '\0';
}
/* if using an external router leave address alone */
if ((Smtpmode & QUEUE) != 0)
return LOCAL;
/* check for the user%host hack */
if ((cp = strrchr(s,'%')) != NULLCHAR) {
*cp = '@';
cp++;
/* reroute based on host name following the % seperator */
if (mailroute(cp) == 0)
return BADADDR;
else
return DOMAIN;
}
#ifdef MSDOS /* dos file name checks */
/* Check for characters illegal in MS-DOS file names */
for(cp = baddoschars;*cp != '\0';cp++){
if(strchr(s,*cp) != NULLCHAR)
return BADADDR;
}
#endif
return LOCAL;
}
/* place a mail job in the outbound queue */
int
queuejob(dfile,host,to,from)
FILE *dfile;
char *host,*to,*from;
{
FILE *fp;
char tmpstring[50];
char prefix[9];
register int c;
sprintf(prefix,"%ld",get_msgid());
smtplog("queue job %s To: %s From: %s",prefix,to,from);
mlock(Mailqdir,prefix);
sprintf(tmpstring,"%s/%s.txt",Mailqdir,prefix);
if((fp = fopen(tmpstring,WRITE_TEXT)) == NULLFILE) {
(void) rmlock(Mailqdir,prefix);
return 1;
}
while((c = getc(dfile)) != EOF)
if(putc(c,fp) == EOF)
break;
if(ferror(fp)){
fclose(fp);
(void) rmlock(Mailqdir,prefix);
return 1;
}
fclose(fp);
sprintf(tmpstring,"%s/%s.wrk",Mailqdir,prefix);
if((fp = fopen(tmpstring,WRITE_TEXT)) == NULLFILE) {
(void) rmlock(Mailqdir,prefix);
return 1;
}
fprintf(fp,"%s\n%s\n%s\n",host,from,to);
fclose(fp);
(void) rmlock(Mailqdir,prefix);
return 0;
}
/* Deliver mail to the appropriate mail boxes */
static int
router_queue(data,from,to)
FILE *data;
char *from;
struct list *to;
{
int c;
register struct list *ap;
FILE *fp;
char tmpstring[50];
char prefix[9];
sprintf(prefix,"%ld",get_msgid());
mlock(Routeqdir,prefix);
sprintf(tmpstring,"%s/%s.txt",Routeqdir,prefix);
if((fp = fopen(tmpstring,WRITE_TEXT)) == NULLFILE) {
(void) rmlock(Routeqdir,prefix);
return 1;
}
rewind(data);
while((c = getc(data)) != EOF)
if(putc(c,fp) == EOF)
break;
if(ferror(fp)){
fclose(fp);
(void) rmlock(Routeqdir,prefix);
return 1;
}
fclose(fp);
sprintf(tmpstring,"%s/%s.wrk",Routeqdir,prefix);
if((fp = fopen(tmpstring,WRITE_TEXT)) == NULLFILE) {
(void) rmlock(Routeqdir,prefix);
return 1;
}
fprintf(fp,"From: %s\n",from);
for(ap = to;ap != NULLLIST;ap = ap->next) {
fprintf(fp,"To: %s\n",ap->val);
}
fclose(fp);
(void) rmlock(Routeqdir,prefix);
smtplog("rqueue job %s From: %s",prefix,from);
return 0;
}
/* add an element to the front of the list pointed to by head
** return NULLLIST if out of memory.
*/
struct list *
addlist(head,val,type)
struct list **head;
char *val;
int type;
{
register struct list *tp;
tp = (struct list *)calloc(1,sizeof(struct list));
if (tp == NULLLIST)
return NULLLIST;
tp->next = NULLLIST;
/* allocate storage for the char string */
if ((tp->val = strdup(val)) == NULLCHAR) {
(void) free((char *)tp);
return NULLLIST;
}
tp->type = type;
/* add entry to front of existing list */
if (*head == NULLLIST)
*head = tp;
else {
tp->next = *head;
*head = tp;
}
return tp;
}
#define SKIPWORD(X) while(*X && *X!=' ' && *X!='\t' && *X!='\n' && *X!= ',') X++;
#define SKIPSPACE(X) while(*X ==' ' || *X =='\t' || *X =='\n' || *X == ',') X++;
/* check for and alias and expand alias into a address list */
static struct list *
expandalias(head, user)
struct list **head;
char *user;
{
FILE *fp;
register char *s,*p;
int inalias;
struct list *tp;
char buf[LINELEN];
/* no alias file found */
if ((fp = fopen(Alias, READ_TEXT)) == NULLFILE)
return addlist(head, user, LOCAL);
inalias = 0;
while (fgets(buf,LINELEN,fp) != NULLCHAR) {
p = buf;
if ( *p == '#' || *p == '\0')
continue;
rip(p);
/* if not in an matching entry skip continuation lines */
if (!inalias && isspace(*p))
continue;
/* when processing an active alias check for a continuation */
if (inalias) {
if (!isspace(*p))
break; /* done */
} else {
s = p;
SKIPWORD(p);
*p++ = '\0'; /* end the alias name */
if (strcmp(s,user) != 0)
continue; /* no match go on */
inalias = 1;
}
/* process the recipients on the alias line */
SKIPSPACE(p);
while(*p != '\0' && *p != '#') {
s = p;
SKIPWORD(p);
if (*p != '\0')
*p++ = '\0';
/* find hostname */
if (strchr(s,'@') != NULLCHAR)
tp = addlist(head,s,DOMAIN);
else
tp = addlist(head,s,LOCAL);
SKIPSPACE(p);
}
}
(void) fclose(fp);
if (inalias) /* found and processed and alias. */
return tp;
/* no alias found treat as a local address */
return addlist(head, user, LOCAL);
}
#if defined(ANSIPROTO) && !defined(AMIGA)
static void
smtplog(char *fmt, ...)
{
va_list ap;
char *cp;
long t;
FILE *fp;
if ((fp = fopen(Maillog,APPEND_TEXT)) == NULLFILE)
return;
time(&t);
cp = ctime(&t);
rip(cp);
fprintf(fp,"%s ",cp);
va_start(ap,fmt);
vfprintf(fp,fmt,ap);
va_end(ap);
fprintf(fp,"\n");
fclose(fp);
}
#else
static void
smtplog(fmt,arg1,arg2,arg3,arg4)
char *fmt;
int arg1,arg2,arg3,arg4;
{
char *cp;
long t;
FILE *fp;
if ((fp = fopen(Maillog,APPEND_TEXT)) == NULLFILE)
return;
time(&t);
cp = ctime(&t);
rip(cp);
fprintf(fp,"%s ",cp);
fprintf(fp,fmt,arg1,arg2,arg3,arg4);
fprintf(fp,"\n");
fclose(fp);
}
#endif
/* send mail to a single user. Can be called from the ax24 mailbox or
** from the return mail function in the smtp client
*/
int
mailuser(data,from,to)
FILE *data;
char *from;
char *to;
{
int address_type, ret;
struct list *tolist = NULLLIST;
/* check if address is ok */
if ((address_type = validate_address(to)) == BADADDR) {
return 1;
}
/* if a local address check for an alias */
if (address_type == LOCAL)
expandalias(&tolist, to);
else
/* a remote address is added to the list */
addlist(&tolist, to, address_type);
ret = mailit(data,from,tolist);
del_list(tolist);
return ret;
}